I need your help!

I want your feedback to make the book better for you and other readers. If you find typos, errors, or places where the text may be improved, please let me know. The best ways to provide feedback are by GitHub or hypothes.is annotations.

Opening an issue or submitting a pull request on GitHub: https://github.com/isaactpetersen/Fantasy-Football-Analytics-Textbook

Hypothesis Adding an annotation using hypothes.is. To add an annotation, select some text and then click the symbol on the pop-up menu. To see the annotations of others, click the symbol in the upper right-hand corner of the page.

3  Getting Started with R for Data Analysis

The book uses R for statistical analyses (http://www.r-project.org). R is a free software environment; you can download it at no charge here: https://cran.r-project.org.

3.1 Initial Setup

To get started, follow the following steps:

  1. Install R: https://cran.r-project.org

  2. Install RStudio Desktop: https://posit.co/download/rstudio-desktop

  3. After installing RStudio, open RStudio and run the following code in the console to install several key R packages:

    Code
    install.packages(
      c("petersenlab","remotes","nflreadr","nflfastR","nfl4th","nflplotR",
      "gsisdecoder","progressr","lubridate","tidyverse","psych"))
  4. Some necessary packages, including the ffanalytics package, are hosted in GitHub and need to be installed using the following code (after installing the remotes package above):

    Code
    remotes::install_github("FantasyFootballAnalytics/ffanalytics")
Note 3.1: If you are in Dr. Petersen’s class

If you are in Dr. Petersen’s class, also perform the following steps:

  1. Set up a free account on GitHub.com.
  2. Download GitHub Desktop: https://desktop.github.com
  3. Make sure you are logged into your GitHub account on GitHub.com.
  4. Go to the following GitHub repository: https://github.com/isaactpetersen/QuartoBlogFantasyFootball and complete the following steps:
    1. Click “Use this Template” (in the top right of the screen) > “Create a new repository”
    2. Make sure the checkbox is selected for the following option: “Include all branches”
    3. Make sure your Owner account is selected
    4. Specify the repository name to whatever you want, such as FantasyFootballBlog
    5. Type a brief description, such as Files for my fantasy football blog
    6. Keep the repository public (this is necessary for generating your blog)
    7. Select “Create repository”
  5. After creating the new repository, make sure you are on the page of of your new repository and complete the following steps:
    1. Click “Settings” (in the topof the screen)
    2. Click “Actions” (in the left sidebar) > “General”
    3. Make sure the following are selected:
      • “Read and write permissions” (under “Workflow permissions”)
      • “Allow GitHub Actions to create and approve pull requests”
      • then click “Save”
    4. Click “Pages” (in the left sidebar)
    5. Make sure the following are selected:
      • “Deploy from a branch” (under “Source”)
      • “gh-pages/(root)” (under “Branch”)
      • then click “Save”
  6. Clone the repository to your local computer by clicking “Code” > “Open with GitHub Desktop”, select the folder where you want the repository to be saved on your local computer, and click “Clone”

3.2 Installing Packages

You can install R packages using the following syntax:

Code
install.packages("INSERT_PACKAGE_NAME_HERE")

For instance, you can use the following code to install the nflreadr package:

Code
install.packages("nflreadr")

3.3 Load Packages

Code
library("ffanalytics")
library("nflreadr")
library("nflfastR")
library("nfl4th")
library("nflplotR")
library("progressr")
library("lubridate")
library("tidyverse")

3.4 Using Functions and Arguments

You can learn about a particular function and its arguments by entering a question mark before the name of the function:

Code
?NAME_OF_FUNCTION()

Below, we provide examples for how to learn about and use functions and arguments, by using the seq() function as an example. The seq() function creates a sequence of numbers. To learn about the seq() function, which creates a sequence of numbers, you can execute the following command:

Code
?seq()

This is what the documentation shows for the seq() function in the Usage section:

Code
seq(
  from = 1,
  to = 1,
  by = ((to - from)/(length.out - 1)),
  length.out = NULL,
  along.with = NULL,
  ...)

Based on this information, we know that the seq() function takes the following arguments:

  • from
  • to
  • by
  • length.out
  • along.with
  • ...

The arguments have default values that are used if the user does not specify values for the arguments. The default values are provided in the Usage section and are in Table 3.1:

Table 3.1: Arguments and defaults for the seq() function. Arguments with a default of NULL are not used unless a value is provided by the user.
Argument Default Value for Argument
from 1
to 1
by ((to - from)/(length.out - 1))
length.out NULL
along.with NULL

What each argument represents (i.e., the meaning of from, to, by, etc.) is provided in the Arguments section of the documentation. You can specify a function and its arguments either by providing values for each argument in the order indicated by the function, or by naming its arguments.

Here is an example of providing values to the arguments in the order indicated by the function, to create a sequence of numbers from 1 to 9:

Code
seq(1, 9)
[1] 1 2 3 4 5 6 7 8 9

Here is an example of providing values to the arguments by naming its arguments:

Code
seq(
  from = 1,
  to = 9,
  by = 1)
[1] 1 2 3 4 5 6 7 8 9

If you provide values to arguments by naming the arguments, you can reorder the arguments and get the same answer:

Code
seq(
  by = 1,
  to = 9,
  from = 1)
[1] 1 2 3 4 5 6 7 8 9

There are various combinations of arguments that one could use to obtain the same result. For instance, here is code to generate a sequence from 1 to 9 by 2:

Code
seq(
  from = 1,
  to = 9,
  by = 2)
[1] 1 3 5 7 9

Or, alternatively, you could specify the length of the desired sequence (5 values):

Code
seq(
  from = 1,
  to = 9,
  length.out = 5)
[1] 1 3 5 7 9

If you want to generate a series with decimal values, you could specify a long desired sequence of 81 values:

Code
seq(
  from = 1,
  to = 9,
  length.out = 81)
 [1] 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8
[20] 2.9 3.0 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 4.0 4.1 4.2 4.3 4.4 4.5 4.6 4.7
[39] 4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 6.0 6.1 6.2 6.3 6.4 6.5 6.6
[58] 6.7 6.8 6.9 7.0 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 7.9 8.0 8.1 8.2 8.3 8.4 8.5
[77] 8.6 8.7 8.8 8.9 9.0

This is equivalent to specifying a sequence from 1 to 9 by 0.1:

Code
seq(
  from = 1,
  to = 9,
  by = 0.1)
 [1] 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8
[20] 2.9 3.0 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 3.9 4.0 4.1 4.2 4.3 4.4 4.5 4.6 4.7
[39] 4.8 4.9 5.0 5.1 5.2 5.3 5.4 5.5 5.6 5.7 5.8 5.9 6.0 6.1 6.2 6.3 6.4 6.5 6.6
[58] 6.7 6.8 6.9 7.0 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 7.9 8.0 8.1 8.2 8.3 8.4 8.5
[77] 8.6 8.7 8.8 8.9 9.0

Hopefully, that provides an example for how to learn about a particular function, its arguments, and how to use them.

3.5 Download Football Data

Below, we provide examples for how to download various types of National Football League (NFL) data. For additional resources, Congelio (2023) provides a helpful introductory text for working with NFL data in R. We save each data file after downloading it, so we can use the data in subsequent chapters.

3.5.1 Players

Code
nfl_players_raw <- progressr::with_progress(
  nflreadr::load_players())
Code
save(
  nfl_players_raw,
  file = "./data/nfl_players_raw.RData"
)

The nfl_players object is in id form. That is, each row should be uniquely identified by gsis_id. Let’s rearrange the data accordingly:

Code
nfl_players <- nfl_players_raw %>% 
  select(gsis_id, everything()) %>% 
  arrange(display_name)

Let’s check for duplicate id instances:

Code
nfl_players %>% 
  group_by(gsis_id) %>% 
  filter(n() > 1)

Let’s do some data cleanup:

Code
# Convert missing values to NA
nfl_players[nfl_players == ""] <- NA

# Drop players with missing values for gsis_id
nfl_players <- nfl_players %>% 
  filter(!is.na(gsis_id))
Code
save(
  nfl_players,
  file = "./data/nfl_players.RData"
)

3.5.2 Teams

Code
nfl_teams_raw <- progressr::with_progress(
  nflreadr::load_teams(current = TRUE))
Code
save(
  nfl_teams_raw,
  file = "./data/nfl_teams_raw.RData"
)

The nfl_teams object is in id form. That is, each row should be uniquely identified by team_id. Let’s rearrange the data accordingly:

Code
nfl_teams <- nfl_teams_raw %>% 
  select(team_id, everything())

Let’s check for duplicate id instances:

Code
nfl_teams %>% 
  group_by(team_id) %>% 
  filter(n() > 1)
Code
save(
  nfl_teams,
  file = "./data/nfl_teams.RData"
)

3.5.3 Player Info

3.5.4 Rosters

A Data Dictionary for rosters is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_rosters.html

Code
nfl_rosters_raw <- progressr::with_progress(
  nflreadr::load_rosters(seasons = TRUE))

nfl_rosters_weekly_raw <- progressr::with_progress(
  nflreadr::load_rosters_weekly(seasons = TRUE))
Code
save(
  nfl_rosters_raw,
  file = "./data/nfl_rosters_raw.RData"
)

save(
  nfl_rosters_weekly_raw,
  file = "./data/nfl_rosters_weekly_raw.RData"
)

The nfl_rosters object is in id-season-team form. That is, each row should be uniquely identified by the combination of gsis_id, season, and team. Let’s rearrange the data accordingly:

Code
nfl_rosters <- nfl_rosters_raw %>% 
  select(gsis_id, season, team, week, everything()) %>% 
  arrange(full_name, gsis_id, season, team, week)

Let’s check for duplicate id instances:

Code
nfl_rosters %>% 
  group_by(gsis_id, season, team) %>% 
  filter(n() > 1)

Let’s do some data cleanup:

Code
# Drop players with missing values for gsis_id
nfl_rosters <- nfl_rosters %>% 
  filter(!is.na(gsis_id))

# Fill in missing values for a player in their duplicate instances, and then keep only the first of the duplicate instances
nfl_rosters <- nfl_rosters %>% 
  group_by(gsis_id, season, team) %>% 
  fill(names(.), .direction = "downup") %>% 
  slice_head(n = 1) %>% 
  ungroup()

Let’s check again for duplicate id instances:

Code
nfl_rosters %>% 
  group_by(gsis_id, season, team) %>% 
  filter(n() > 1)

The nfl_rosters_weekly object is in id-season-week form. That is, each row should be uniquely identified by the combination of gsis_id, season, and week. Let’s rearrange the data accordingly:

Code
nfl_rosters_weekly <- nfl_rosters_weekly_raw %>% 
  select(gsis_id, season, week, everything()) %>% 
  arrange(full_name, gsis_id, season, week)

Let’s check for duplicate id instances:

Code
nfl_rosters_weekly %>% 
  group_by(gsis_id, season, week) %>% 
  filter(n() > 1)

Let’s do some data cleanup:

Code
# Drop players with missing values for gsis_id
nfl_rosters_weekly <- nfl_rosters_weekly %>% 
  filter(!is.na(gsis_id))

# Fill in missing values for a player in their duplicate instances, and then keep only the first of the duplicate instances
nfl_rosters_weekly <- nfl_rosters_weekly %>% 
  group_by(gsis_id, season, week) %>% 
  fill(names(.), .direction = "downup") %>% 
  slice_head(n = 1) %>% 
  ungroup()

Let’s check again for duplicate id instances:

Code
nfl_rosters_weekly %>% 
  group_by(gsis_id, season, week) %>% 
  filter(n() > 1)
Code
save(
  nfl_rosters,
  file = "./data/nfl_rosters.RData"
)

save(
  nfl_rosters_weekly,
  file = "./data/nfl_rosters_weekly.RData"
)

3.5.5 Game Schedules

A Data Dictionary for game schedules data is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_schedules.html

Code
nfl_schedules_raw <- progressr::with_progress(
  nflreadr::load_schedules(seasons = TRUE))
Code
save(
  nfl_schedules_raw,
  file = "./data/nfl_schedules_raw.RData"
)

The nfl_schedules object is in id form. That is, each row should be uniquely identified by game_id.

Code
nfl_schedules <- nfl_schedules_raw

Let’s check for duplicate id instances:

Code
nfl_schedules %>% 
  group_by(game_id) %>% 
  filter(n() > 1)
Code
save(
  nfl_schedules,
  file = "./data/nfl_schedules.RData"
)

3.5.6 The Combine

A Data Dictionary for data from the combine is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_combine.html

Code
nfl_combine_raw <- progressr::with_progress(
  nflreadr::load_combine(seasons = TRUE))
Code
save(
  nfl_combine_raw,
  file = "./data/nfl_combine_raw.RData"
)

The nfl_combine object is in id form. That is, each row should be uniquely identified by their id. However, there is no gsis_id variable to merge it easily with other datasets. Some of the players have other id variables, including pfr_id and cfb_id. Let’s rearrange the data accordingly:

Code
nfl_combine <- nfl_combine_raw %>% 
  select(pfr_id, cfb_id, everything()) %>% 
  arrange(season, player_name)

Let’s check for duplicate id instances:

Code
nfl_combine %>% 
  group_by(pfr_id) %>% 
  filter(n() > 1, !is.na(pfr_id)) %>% 
  arrange(pfr_id)
Code
nfl_combine %>% 
  group_by(cfb_id) %>% 
  filter(n() > 1, !is.na(cfb_id)) %>% 
  arrange(cfb_id)

However, these apparent duplicates appear to be different players:

Code
nfl_combine %>% 
  group_by(season, pfr_id, pos) %>% 
  filter(n() > 1, !is.na(pfr_id)) %>% 
  arrange(pfr_id)
Code
nfl_combine %>% 
  group_by(season, cfb_id, pos) %>% 
  filter(n() > 1, !is.na(cfb_id)) %>% 
  arrange(cfb_id)
Code
save(
  nfl_combine,
  file = "./data/nfl_combine.RData"
)

3.5.7 Draft Picks

A Data Dictionary for draft picks data is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_draft_picks.html

Code
nfl_draftPicks_raw <- progressr::with_progress(
  nflreadr::load_draft_picks(seasons = TRUE))
Code
save(
  nfl_draftPicks_raw,
  file = "./data/nfl_draftPicks_raw.RData"
)

The nfl_draftPicks object is in id form. That is, each row should be uniquely identified by gsis_id. Let’s rearrange the data accordingly:

Code
nfl_draftPicks <- nfl_draftPicks_raw %>% 
  select(gsis_id, everything()) %>% 
  arrange(pfr_player_name)

Let’s check for duplicate id instances:

Code
nfl_draftPicks %>% 
  group_by(gsis_id) %>% 
  filter(n() > 1)

Let’s do some data cleanup:

Code
# Convert missing values to NA
nfl_draftPicks[nfl_draftPicks == ""] <- NA

# Drop players with missing values for gsis_id
nfl_draftPicks <- nfl_draftPicks %>% 
  filter(!is.na(gsis_id))

Let’s check again for duplicate id instances:

Code
nfl_draftPicks %>% 
  group_by(gsis_id) %>% 
  filter(n() > 1)
Code
save(
  nfl_draftPicks,
  file = "./data/nfl_draftPicks.RData"
)

3.5.8 Depth Charts

A Data Dictionary for data from weekly depth charts is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_depth_charts.html

Code
nfl_depthCharts_raw <- progressr::with_progress(
  nflreadr::load_depth_charts(seasons = TRUE))
Code
save(
  nfl_depthCharts_raw,
  file = "./data/nfl_depthCharts_raw.RData"
)

The nfl_depthCharts object is in id-season-week-position form. That is, each row should be uniquely identified by the combination of gsis_id, season, week, and depth_position. Let’s rearrange the data accordingly:

Code
nfl_depthCharts <- nfl_depthCharts_raw %>% 
  select(gsis_id, season, week, depth_position, everything()) %>% 
  arrange(full_name, gsis_id, season, week, depth_position)

Let’s check for duplicate id instances:

Code
nfl_depthCharts %>% 
  group_by(gsis_id, season, week, depth_position) %>% 
  filter(n() > 1)

Let’s do some data cleanup:

Code
# Drop players with missing values for gsis_id
nfl_depthCharts <- nfl_depthCharts %>% 
  filter(!is.na(gsis_id))

# Fill in missing values for a player in their duplicate instances, and then keep only the first of the duplicate instances
nfl_depthCharts <- nfl_depthCharts %>% 
  group_by(gsis_id, season, week, depth_position) %>% 
  fill(names(.), .direction = "downup") %>% 
  slice_head(n = 1) %>% 
  ungroup()

Let’s check again for duplicate id instances:

Code
nfl_depthCharts %>% 
  group_by(gsis_id, season, week, depth_position) %>% 
  filter(n() > 1)
Code
save(
  nfl_depthCharts,
  file = "./data/nfl_depthCharts.RData"
)

3.5.9 Play-By-Play Data

To download play-by-play data from prior weeks and seasons, we can use the load_pbp() function of the nflreadr package. We add a progress bar using the with_progress() function from the progressr package because it takes a while to run. A Data Dictionary for the play-by-play data is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_pbp.html

Note 3.2: Downloading play-by-play data

Note: the following code takes a while to run.

Code
nfl_pbp_raw <- progressr::with_progress(
  nflreadr::load_pbp(seasons = TRUE))
Code
save(
  nfl_pbp_raw,
  file = "./data/nfl_pbp_raw.RData"
)

The nfl_pbp object is in game_id-drive-play_id form. That is, each row should be uniquely identified by the combination of game_id, drive, play_id. Let’s rearrange the data accordingly:

Code
nfl_pbp <- nfl_pbp_raw %>% 
  select(game_id, drive, play_id, everything()) %>% 
  arrange(game_id, drive, play_id)

Let’s check for duplicate id instances:

Code
nfl_pbp %>% 
  group_by(game_id, drive, play_id) %>% 
  filter(n() > 1)
Code
save(
  nfl_pbp,
  file = "./data/nfl_pbp.RData"
)

3.5.10 4th Down Data

Note 3.3: Downloading 4th down data

Note: the following code takes a while to run.

Code
nfl_4thdown <- nfl4th::load_4th_pbp(seasons = 2014:2023)
Code
save(
  nfl_4thdown,
  file = "./data/nfl_4thdown.RData"
)

3.5.11 Participation

A Data Dictionary for the participation data is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_participation.html

Code
nfl_participation <- progressr::with_progress(
  nflreadr::load_participation(
    seasons = TRUE,
    include_pbp = TRUE))
Code
save(
  nfl_participation,
  file = "./data/nfl_participation.RData"
)

3.5.12 Historical Weekly Actual Player Statistics

We can download historical week-by-week actual player statistics using the load_player_stats() function from the nflreadr package. A Data Dictionary for statistics for offensive players is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_player_stats.html. A Data Dictionary for statistics for defensive players is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_player_stats_def.html.

Code
nfl_actualStats_offense_weekly <- progressr::with_progress(
  nflreadr::load_player_stats(
    seasons = TRUE,
    stat_type = "offense"))

nfl_actualStats_defense_weekly <- progressr::with_progress(
  nflreadr::load_player_stats(
    seasons = TRUE,
    stat_type = "defense"))

nfl_actualStats_kicking_weekly <- progressr::with_progress(
  nflreadr::load_player_stats(
    seasons = TRUE,
    stat_type = "kicking"))
Code
save(
  nfl_actualStats_offense_weekly, nfl_actualStats_defense_weekly, nfl_actualStats_kicking_weekly,
  file = "./data/nfl_actualStats_weekly.RData"
)

3.5.13 Injuries

A Data Dictionary for injury data is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_injuries.html

Code
nfl_injuries <- progressr::with_progress(
  nflreadr::load_injuries(seasons = TRUE))
Code
save(
  nfl_injuries,
  file = "./data/nfl_injuries.RData"
)

3.5.14 Snap Counts

A Data Dictionary for snap counts data is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_snap_counts.html

Code
nfl_snapCounts <- progressr::with_progress(
  nflreadr::load_snap_counts(seasons = TRUE))
Code
save(
  nfl_snapCounts,
  file = "./data/nfl_snapCounts.RData"
)

3.5.15 ESPN QBR

A Data Dictionary for ESPN QBR data is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_espn_qbr.html

Code
nfl_espnQBR_seasonal <- progressr::with_progress(
  nflreadr::load_espn_qbr(
    seasons = TRUE,
    summary_type = c("season")))

nfl_espnQBR_weekly <- progressr::with_progress(
  nflreadr::load_espn_qbr(
    seasons = TRUE,
    summary_type = c("weekly")))

nfl_espnQBR_weekly$game_week <- as.character(nfl_espnQBR_weekly$game_week)

nfl_espnQBR <- bind_rows(
  nfl_espnQBR_seasonal,
  nfl_espnQBR_weekly
)
Code
save(
  nfl_espnQBR,
  file = "./data/nfl_espnQBR.RData"
)

3.5.16 NFL Next Gen Stats

A Data Dictionary for NFL Next Gen Stats data is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_nextgen_stats.html

Code
nfl_nextGenStats_pass_weekly <- progressr::with_progress(
  nflreadr::load_nextgen_stats(
    seasons = TRUE,
    stat_type = c("passing")))

nfl_nextGenStats_rush_weekly <- progressr::with_progress(
  nflreadr::load_nextgen_stats(
    seasons = TRUE,
    stat_type = c("rushing")))

nfl_nextGenStats_rec_weekly <- progressr::with_progress(
  nflreadr::load_nextgen_stats(
    seasons = TRUE,
    stat_type = c("receiving")))

nfl_nextGenStats_weekly <- bind_rows(
  nfl_nextGenStats_pass_weekly,
  nfl_nextGenStats_rush_weekly,
  nfl_nextGenStats_rec_weekly
)
Code
save(
  nfl_nextGenStats_weekly,
  file = "./data/nfl_nextGenStats_weekly.RData"
)

3.5.17 Advanced Stats from Pro Football Reference

A Data Dictionary for Pro Football Reference passing data is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_pfr_passing.html

Code
nfl_advancedStatsPFR_pass_seasonal <- progressr::with_progress(
  nflreadr::load_pfr_advstats(
    seasons = TRUE,
    stat_type = c("pass"),
    summary_level = c("season")))

nfl_advancedStatsPFR_pass_weekly <- progressr::with_progress(
  nflreadr::load_pfr_advstats(
    seasons = TRUE,
    stat_type = c("pass"),
    summary_level = c("week")))

nfl_advancedStatsPFR_rush_seasonal <- progressr::with_progress(
  nflreadr::load_pfr_advstats(
    seasons = TRUE,
    stat_type = c("rush"),
    summary_level = c("season")))

nfl_advancedStatsPFR_rush_weekly <- progressr::with_progress(
  nflreadr::load_pfr_advstats(
    seasons = TRUE,
    stat_type = c("rush"),
    summary_level = c("week")))

nfl_advancedStatsPFR_rec_seasonal <- progressr::with_progress(
  nflreadr::load_pfr_advstats(
    seasons = TRUE,
    stat_type = c("rec"),
    summary_level = c("season")))

nfl_advancedStatsPFR_rec_weekly <- progressr::with_progress(
  nflreadr::load_pfr_advstats(
    seasons = TRUE,
    stat_type = c("rec"),
    summary_level = c("week")))

nfl_advancedStatsPFR_def_seasonal <- progressr::with_progress(
  nflreadr::load_pfr_advstats(
    seasons = TRUE,
    stat_type = c("def"),
    summary_level = c("season")))

nfl_advancedStatsPFR_def_weekly <- progressr::with_progress(
  nflreadr::load_pfr_advstats(
    seasons = TRUE,
    stat_type = c("def"),
    summary_level = c("week")))

nfl_advancedStatsPFR_seasonal <- bind_rows(
  nfl_advancedStatsPFR_pass_seasonal,
  nfl_advancedStatsPFR_rush_seasonal,
  nfl_advancedStatsPFR_rec_seasonal,
  nfl_advancedStatsPFR_def_seasonal
)

nfl_advancedStatsPFR_weekly <- bind_rows(
  nfl_advancedStatsPFR_pass_weekly,
  nfl_advancedStatsPFR_rush_weekly,
  nfl_advancedStatsPFR_rec_weekly,
  nfl_advancedStatsPFR_def_weekly
)

Merge with gsis_id for merging with other datasets:

Code
# Prepare data for merging
nfl_advancedStatsPFR_weekly <- nfl_advancedStatsPFR_weekly %>% 
  rename(pfr_id = pfr_player_id)

# Identify duplicates
nfl_advancedStatsPFR_seasonal %>% 
  select(pfr_id, season, age) %>% 
  na.omit() %>% 
  unique() %>% 
  group_by(pfr_id, season) %>% 
  filter(n() > 1) %>% 
  arrange(pfr_id, season)

# Merge seasonal data with the player IDs
nfl_advancedStatsPFR_seasonal <- left_join(
  nfl_advancedStatsPFR_seasonal,
  nfl_playerIDs %>% 
    filter(!is.na(pfr_id)) %>% 
    filter(gsis_id != "00-0039137") %>% # drop DL Byron Young, keep OLB Byron Young
    select(pfr_id, gsis_id) %>% 
    unique(),
  by = "pfr_id"
)

# Merge weekly data with the player IDs
nfl_advancedStatsPFR_weekly <- left_join(
  nfl_advancedStatsPFR_weekly,
  nfl_playerIDs %>% 
    filter(!is.na(pfr_id)) %>% 
    filter(gsis_id != "00-0039137") %>% # drop DL Byron Young, keep OLB Byron Young
    select(pfr_id, gsis_id) %>% 
    unique(),
  by = "pfr_id"
)

# Remove distinct players who were given the same `pfr_id` (to allow merging)
nfl_advancedStatsPFR_seasonal$gsis_id[which(nfl_advancedStatsPFR_seasonal$gsis_id == "00-0035665" & nfl_advancedStatsPFR_seasonal$pos %in% c("LB","LILB","RILB"))] <- NA # drop LB David Young, keep DB David Young
#nfl_advancedStatsPFR_weekly$gsis_id[which(nfl_advancedStatsPFR_weekly$gsis_id == "00-0035665" & nfl_advancedStatsPFR_weekly$team %in% c("TEN","MIA"))] <- NA # drop LB David Young, keep DB David Young

nfl_advancedStatsPFR_seasonal$gsis_id[which(nfl_advancedStatsPFR_seasonal$gsis_id == "00-0035292" & nfl_advancedStatsPFR_seasonal$pos %in% c("LB","LILB","RILB"))] <- NA # drop LB David Young, keep DB David Young
nfl_advancedStatsPFR_weekly$gsis_id[which(nfl_advancedStatsPFR_weekly$gsis_id == "00-0035292" & nfl_advancedStatsPFR_weekly$team %in% c("TEN","MIA"))] <- NA # drop LB David Young, keep DB David Young

nfl_advancedStatsPFR_seasonal$gsis_id[which(nfl_advancedStatsPFR_seasonal$gsis_id == "00-0033894" & nfl_advancedStatsPFR_seasonal$pos == "DB")] <- NA # drop S Marcus Williams, keep DB David Young
#nfl_advancedStatsPFR_weekly$gsis_id[which(nfl_advancedStatsPFR_weekly$gsis_id == "00-0033894" & nfl_advancedStatsPFR_weekly$pos == "DB")] <- NA # drop S Marcus Williams

nfl_advancedStatsPFR_seasonal$gsis_id[which(nfl_advancedStatsPFR_seasonal$gsis_id == "00-0038407" & nfl_advancedStatsPFR_seasonal$pos == "DB")] <- NA # drop DB Jaylon Jones, keep CB Jaylon Jones
#nfl_advancedStatsPFR_weekly$gsis_id[which(nfl_advancedStatsPFR_weekly$gsis_id == "00-0038407" & nfl_advancedStatsPFR_weekly$pos == "DB")] <- NA # drop DB Jaylon Jones, keep CB Jaylon Jones

nfl_advancedStatsPFR_seasonal$gsis_id[which(nfl_advancedStatsPFR_seasonal$gsis_id == "00-0037106" & nfl_advancedStatsPFR_seasonal$pos == "DB")] <- NA # drop DB Jaylon Jones, keep CB Jaylon Jones
#nfl_advancedStatsPFR_weekly$gsis_id[which(nfl_advancedStatsPFR_weekly$gsis_id == "00-0037106" & nfl_advancedStatsPFR_weekly$pos == "DB")] <- NA # drop DB Jaylon Jones, keep CB Jaylon Jones

nfl_advancedStatsPFR_seasonal$gsis_id[which(nfl_advancedStatsPFR_seasonal$gsis_id == "00-0038549" & nfl_advancedStatsPFR_seasonal$pos == "WR")] <- NA # drop WR DJ TUrner, keep CB DJ Turner
#nfl_advancedStatsPFR_weekly$gsis_id[which(nfl_advancedStatsPFR_weekly$gsis_id == "00-0038549" & nfl_advancedStatsPFR_weekly$pos == "WR")] <- NA # drop WR DJ TUrner, keep CB DJ Turner
Code
save(
  nfl_advancedStatsPFR_seasonal,
  file = "./data/nfl_advancedStatsPFR_seasonal.RData"
)

save(
  nfl_advancedStatsPFR_weekly,
  file = "./data/nfl_advancedStatsPFR_weekly.RData"
)

3.5.18 Player Contracts

A Data Dictionary for player contracts data is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_contracts.html

Code
nfl_playerContracts <- progressr::with_progress(
  nflreadr::load_contracts())
Code
save(
  nfl_playerContracts,
  file = "./data/nfl_playerContracts.RData"
)

3.5.19 FTN Charting Data

A Data Dictionary for FTN Charting data is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_ftn_charting.html

Code
nfl_ftnCharting <- progressr::with_progress(
  nflreadr::load_ftn_charting(seasons = TRUE))
Code
save(
  nfl_ftnCharting,
  file = "./data/nfl_ftnCharting.RData"
)

3.5.20 Fantasy Player IDs

A Data Dictionary for fantasy player ID data is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_ff_playerids.html

Code
nfl_playerIDs <- progressr::with_progress(
  nflreadr::load_ff_playerids())
Code
save(
  nfl_playerIDs,
  file = "./data/nfl_playerIDs.RData"
)

3.5.21 FantasyPros Rankings

A Data Dictionary for FantasyPros ranking data is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_ff_rankings.html

Code
#nfl_rankings <- progressr::with_progress( # currently throws error
#  nflreadr::load_ff_rankings(type = "all"))

nfl_rankings_draft <- progressr::with_progress(
  nflreadr::load_ff_rankings(type = "draft"))

nfl_rankings_weekly <- progressr::with_progress(
  nflreadr::load_ff_rankings(type = "week"))

nfl_rankings <- bind_rows(
  nfl_rankings_draft,
  nfl_rankings_weekly
)
Code
save(
  nfl_rankings,
  file = "./data/nfl_rankings.RData"
)

3.5.22 Expected Fantasy Points

A Data Dictionary for expected fantasy points data is located at the following link: https://nflreadr.nflverse.com/articles/dictionary_ff_opportunity.html

Code
nfl_expectedFantasyPoints_weekly <- progressr::with_progress(
  nflreadr::load_ff_opportunity(
    seasons = TRUE,
    stat_type = "weekly",
    model_version = "latest"
  ))

nfl_expectedFantasyPoints_pass <- progressr::with_progress(
  nflreadr::load_ff_opportunity(
    seasons = TRUE,
    stat_type = "pbp_pass",
    model_version = "latest"
  ))

nfl_expectedFantasyPoints_rush <- progressr::with_progress(
  nflreadr::load_ff_opportunity(
    seasons = TRUE,
    stat_type = "pbp_rush",
    model_version = "latest"
  ))

nfl_expectedFantasyPoints_weekly$season <- as.integer(nfl_expectedFantasyPoints_weekly$season)

nfl_expectedFantasyPoints_pbp <- bind_rows(
  nfl_expectedFantasyPoints_pass,
  nfl_expectedFantasyPoints_rush
)
Code
save(
  nfl_expectedFantasyPoints_weekly,
  file = "./data/nfl_expectedFantasyPoints_weekly.RData"
)

save(
  nfl_expectedFantasyPoints_pbp,
  file = "./data/nfl_expectedFantasyPoints_pbp.RData"
)

3.6 Data Dictionary

Data Dictionaries are metadata that describe the meaning of the variables in a datset. You can find Data Dictionaries for the various NFL datasets at the following link: https://nflreadr.nflverse.com/articles/index.html.

3.7 Create a Data Frame

Here is an example of creating a data frame:

Code
players <- data.frame(
  ID = 1:12,
  name = c(
    "Ken Cussion",
    "Ben Sacked",
    "Chuck Downfield",
    "Ron Ingback",
    "Rhonda Ball",
    "Hugo Long",
    "Lionel Scrimmage",
    "Drew Blood",
    "Chase Emdown",
    "Justin Time",
    "Spike D'Ball",
    "Isac Ulooz"),
  position = c("QB","QB","QB","RB","RB","WR","WR","WR","WR","TE","TE","LB"),
  age = c(40, 30, 24, 20, 18, 23, 27, 32, 26, 23, NA, 37)
  )

fantasyPoints <- data.frame(
  ID = c(2, 7, 13, 14),
  fantasyPoints = c(250, 170, 65, 15)
)

3.8 Variable Names

To see the names of variables in a data frame, use the following syntax:

Code
names(nfl_players)
 [1] "gsis_id"                  "status"                  
 [3] "display_name"             "first_name"              
 [5] "last_name"                "esb_id"                  
 [7] "birth_date"               "college_name"            
 [9] "position_group"           "position"                
[11] "jersey_number"            "height"                  
[13] "weight"                   "years_of_experience"     
[15] "team_abbr"                "team_seq"                
[17] "current_team_id"          "football_name"           
[19] "entry_year"               "rookie_year"             
[21] "draft_club"               "draft_number"            
[23] "college_conference"       "status_description_abbr" 
[25] "status_short_description" "gsis_it_id"              
[27] "short_name"               "smart_id"                
[29] "headshot"                 "suffix"                  
[31] "uniform_number"           "draft_round"             
[33] "season"                  
Code
names(players)
[1] "ID"       "name"     "position" "age"     
Code
names(fantasyPoints)
[1] "ID"            "fantasyPoints"

3.9 Logical Operators

3.9.1 Is Equal To: ==

Code
players$position == "RB"
 [1] FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE

3.9.2 Is Not Equal To: !=

Code
players$position != "RB"
 [1]  TRUE  TRUE  TRUE FALSE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE

3.9.3 Is Greater Than: >

Code
players$age > 30
 [1]  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE    NA  TRUE

3.9.4 Is Less Than: <

Code
players$age < 30
 [1] FALSE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE    NA FALSE

3.9.5 Is Greater Than or Equal To: >=

Code
players$age >= 30
 [1]  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE    NA  TRUE

3.9.6 Is Less Than or Equal To: <=

Code
players$age <= 30
 [1] FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE    NA FALSE

3.9.7 Is In a Value of Another Vector: %in%

Code
players$position %in% c("RB","WR")
 [1] FALSE FALSE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE

3.9.8 Is Not In a Value of Another Vector: !(%in%)

Code
!(players$position %in% c("RB","WR"))
 [1]  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE

3.9.9 Is Missing: is.na()

Code
is.na(players$age)
 [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE

3.9.10 Is Not Missing: !is.na()

Code
!is.na(players$age)
 [1]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE

3.9.11 And: &

Code
players$position == "WR" & players$age > 26
 [1] FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE

3.9.12 Or: |

Code
players$position == "WR" | players$age > 23
 [1]  TRUE  TRUE  TRUE FALSE FALSE  TRUE  TRUE  TRUE  TRUE FALSE    NA  TRUE

3.10 Subset

To subset a data frame, use brackets to specify the subset of rows and columns to keep, where the value/vector before the comma specifies the rows to keep, and the value/vector after the comma specifies the columns to keep:

Code
dataframe[rowsToKeep, columnsToKeep]

You can subset by using any of the following:

  • numeric indices of the rows/columns to keep (or drop)
  • names of the rows/columns to keep (or drop)
  • values of TRUE and FALSE corresponding to which rows/columns to keep

3.10.1 One Variable

To subset one variable, use the following syntax:

Code
players$name
 [1] "Ken Cussion"      "Ben Sacked"       "Chuck Downfield"  "Ron Ingback"     
 [5] "Rhonda Ball"      "Hugo Long"        "Lionel Scrimmage" "Drew Blood"      
 [9] "Chase Emdown"     "Justin Time"      "Spike D'Ball"     "Isac Ulooz"      

or:

Code
players[,"name"]
 [1] "Ken Cussion"      "Ben Sacked"       "Chuck Downfield"  "Ron Ingback"     
 [5] "Rhonda Ball"      "Hugo Long"        "Lionel Scrimmage" "Drew Blood"      
 [9] "Chase Emdown"     "Justin Time"      "Spike D'Ball"     "Isac Ulooz"      

3.10.2 Particular Rows of One Variable

To subset one variable, use the following syntax:

Code
players$name[which(players$position == "RB")]
[1] "Ron Ingback" "Rhonda Ball"

or:

Code
players[which(players$position == "RB"), "name"]
[1] "Ron Ingback" "Rhonda Ball"

3.10.3 Particular Columns (Variables)

To subset particular columns/variables, use the following syntax:

3.10.3.1 Base R

Code
subsetVars <- c("name","age")

players[,c(2,4)]
Code
players[,c("name","age")]
Code
players[,subsetVars]

Or, to drop columns:

Code
dropVars <- c("name","age")

players[,-c(2,4)]
Code
players[,!(names(players) %in% c("name","age"))]
Code
players[,!(names(players) %in% dropVars)]

3.10.3.2 Tidyverse

Code
players %>%
  select(name, age)
Code
players %>%
  select(name:age)
Code
players %>%
  select(all_of(subsetVars))

Or, to drop columns:

Code
players %>%
  select(-name, -age)
Code
players %>%
  select(-c(name:age))
Code
players %>%
  select(-all_of(dropVars))

3.10.4 Particular Rows

To subset particular rows, use the following syntax:

3.10.4.1 Base R

Code
subsetRows <- c(4,5)

players[c(4,5),]
Code
players[subsetRows,]
Code
players[which(players$position == "RB"),]

3.10.4.2 Tidyverse

Code
players %>%
  filter(position == "WR")
Code
players %>%
  filter(position == "WR", age <= 26)
Code
players %>%
  filter(position == "WR" | age >= 26)

3.10.5 Particular Rows and Columns

To subset particular rows and columns, use the following syntax:

3.10.5.1 Base R

Code
players[c(4,5), c(2,4)]
Code
players[subsetRows, subsetVars]
Code
players[which(players$position == "RB"), subsetVars]

3.10.5.2 Tidyverse

Code
players %>%
  filter(position == "RB") %>%
  select(all_of(subsetVars))

3.11 View Data

3.11.1 All Data

To view data, use the following syntax:

Code
View(players)

3.11.2 First 6 Rows/Elements

To view only the first six rows (if a data frame) or elements (if a vector), use the following syntax:

Code
head(nfl_players)
Code
head(nfl_players$display_name)
[1] "'Omar Ellison"    "A'Shawn Robinson" "A.J. Arcuri"      "A.J. Barner"     
[5] "A.J. Bouye"       "A.J. Brown"      

3.12 Data Characteristics

3.12.1 Data Structure

Code
str(nfl_players)
nflvrs_d [20,721 × 33] (S3: nflverse_data/tbl_df/tbl/data.table/data.frame)
 $ gsis_id                 : chr [1:20721] "00-0004866" "00-0032889" "00-0037845" "00-0039793" ...
 $ status                  : chr [1:20721] "RET" "ACT" "ACT" "ACT" ...
 $ display_name            : chr [1:20721] "'Omar Ellison" "A'Shawn Robinson" "A.J. Arcuri" "A.J. Barner" ...
 $ first_name              : chr [1:20721] "'Omar" "A'Shawn" "A.J." "A.J." ...
 $ last_name               : chr [1:20721] "Ellison" "Robinson" "Arcuri" "Barner" ...
 $ esb_id                  : chr [1:20721] "ELL711319" "ROB367960" "ARC716900" NA ...
 $ birth_date              : chr [1:20721] NA "1995-03-21" NA NA ...
 $ college_name            : chr [1:20721] NA "Alabama" "Michigan State" "Michigan" ...
 $ position_group          : chr [1:20721] "WR" "DL" "OL" "TE" ...
 $ position                : chr [1:20721] "WR" "DT" "T" "TE" ...
 $ jersey_number           : int [1:20721] 84 94 61 88 24 11 60 6 81 63 ...
 $ height                  : num [1:20721] 73 76 79 78 72 72 75 76 69 76 ...
 $ weight                  : int [1:20721] 200 330 320 251 191 226 325 220 190 280 ...
 $ years_of_experience     : chr [1:20721] "2" "8" "2" NA ...
 $ team_abbr               : chr [1:20721] "LAC" "CAR" "LA" "SEA" ...
 $ team_seq                : int [1:20721] NA 1 NA NA 1 1 1 1 NA NA ...
 $ current_team_id         : chr [1:20721] "4400" "0750" "2510" "4600" ...
 $ football_name           : chr [1:20721] NA "A'Shawn" "A.J." "A.J." ...
 $ entry_year              : int [1:20721] NA 2016 2022 2024 2013 2019 2015 2019 NA NA ...
 $ rookie_year             : int [1:20721] NA 2016 2022 2024 2013 2019 2015 2019 NA NA ...
 $ draft_club              : chr [1:20721] NA "DET" "LA" "SEA" ...
 $ draft_number            : int [1:20721] NA 46 261 121 NA 51 67 NA NA NA ...
 $ college_conference      : chr [1:20721] NA "Southeastern Conference" "Big Ten Conference" "Big Ten Conference" ...
 $ status_description_abbr : chr [1:20721] NA "A01" "A01" "A01" ...
 $ status_short_description: chr [1:20721] NA "Active" "Active" "Active" ...
 $ gsis_it_id              : int [1:20721] NA 43335 54726 57242 40688 47834 42410 48335 NA NA ...
 $ short_name              : chr [1:20721] NA "A.Robinson" "A.Arcuri" "A.Barner" ...
 $ smart_id                : chr [1:20721] "3200454c-4c71-1319-728e-d49d3d236f8f" "3200524f-4236-7960-bf20-bc060ac0f49c" "32004152-4371-6900-5185-8cdd66b2ad11" NA ...
 $ headshot                : chr [1:20721] NA "https://static.www.nfl.com/image/private/f_auto,q_auto/league/qgiwxchd1lmgszfunys8" NA NA ...
 $ suffix                  : chr [1:20721] NA NA NA NA ...
 $ uniform_number          : chr [1:20721] NA "94" "61" "88" ...
 $ draft_round             : chr [1:20721] NA NA NA NA ...
 $ season                  : int [1:20721] NA NA NA NA NA NA NA NA NA NA ...
 - attr(*, "nflverse_type")= chr "players"
 - attr(*, "nflverse_timestamp")= POSIXct[1:1], format: "2024-08-01 01:29:00"

3.12.2 Data Dimensions

Number of rows and columns:

Code
dim(nfl_players)
[1] 20721    33

Number of rows:

Code
nrow(nfl_players)
[1] 20721

Number of columns:

Code
ncol(nfl_players)
[1] 33

3.12.3 Number of Elements

Code
length(nfl_players$display_name)
[1] 20721

3.12.4 Number of Missing Elements

Code
length(nfl_players$college_name[which(is.na(nfl_players$college_name))])
[1] 12126

3.12.5 Number of Non-Missing Elements

Code
length(nfl_players$college_name[which(!is.na(nfl_players$college_name))])
[1] 8595
Code
length(na.omit(nfl_players$college_name))
[1] 8595

3.13 Create New Variables

To create a new variable, use the following syntax:

Code
players$newVar <- NA

Here is an example of creating a new variable:

Code
players$newVar <- 1:nrow(players)

3.14 Recode Variables

Here is an example of recoding a variable:

Code
players$oldVar1 <- NA
players$oldVar1[which(players$position == "QB")] <- "quarterback"
players$oldVar1[which(players$position == "RB")] <- "running back"
players$oldVar1[which(players$position == "WR")] <- "wide receiver"
players$oldVar1[which(players$position == "TE")] <- "tight end"

players$oldVar2 <- NA
players$oldVar2[which(players$age < 30)] <- "young"
players$oldVar2[which(players$age >= 30)] <- "old"

Recode multiple variables:

Code
players %>%
  mutate(across(c(
    oldVar1:oldVar2),
    ~ case_match(
      .,
      c("quarterback","old","running back") ~ 0,
      c("wide receiver","tight end","young") ~ 1)))

3.15 Rename Variables

Code
players <- players %>% 
  rename(
    newVar1 = oldVar1,
    newVar2 = oldVar2)

Using a vector of variable names:

Code
varNamesFrom <- c("oldVar1","oldVar2")
varNamesTo <- c("newVar1","newVar2")

players <- players %>% 
  rename_with(~ varNamesTo, all_of(varNamesFrom))

3.16 Convert the Types of Variables

One variable:

Code
players$factorVar <- factor(players$ID)
players$numericVar <- as.numeric(players$age)
players$integerVar <- as.integer(players$newVar1)
players$characterVar <- as.character(players$newVar2)

Multiple variables:

Code
players %>%
  mutate(across(c(
    ID,
    age),
    as.numeric))
Code
players %>%
  mutate(across(
    age:newVar1,
    as.character))
Code
players %>%
  mutate(across(where(is.factor), as.character))

3.17 Merging/Joins

3.17.1 Overview

Merging (also called joining) merges two data objects using a shared set of variables called “keys.” The keys are the variable(s) that uniquely identify each row (i.e., they account for the levels of nesting). In some data objects, the key might be the player’s identification number (e.g., player_id). However, some data objects have multiple keys. For instance, in long form data objects, each participant may have multiple rows corresponding to multiple seasons. In this case, the keys may be player_id and season. If a participant has multiple rows corresponding to seasons and games/weeks, the keys are player_id, season, and week. In general, each row should have a value on each of the keys; there should be no missingness in the keys.

To merge two objects, the key(s) that will be used to match the records must be present in both objects. The keys are used to merge the variables in object 1 (x) with the variables in object 2 (y). Different merge types select different rows to merge.

Note: if the two objects include variables with the same name (apart from the keys), R will not know how you want each to appear in the merged object. So, it will add a suffix (e.g., .x, .y) to each common variable to indicate which object (i.e., object x or object y) the variable came from, where object x is the first object—i.e., the object to which object y (the second object) is merged. In general, apart from the keys, you should not include variables with the same name in two objects to be merged. To prevent this, either remove or rename the shared variable in one of the objects, or include the shared variable as a key. However, as described above, you should include it as a key only if it uniquely identifies each row in terms of levels of nesting.

3.17.2 Data Before Merging

Here are the data in the players object:

Code
players
Code
dim(players)
[1] 12 10

The data are structured in ID form. That is, every row in the dataset is uniquely identified by the variable, ID.

Here are the data in the fantasyPoints object:

Code
fantasyPoints
Code
dim(fantasyPoints)
[1] 4 2

3.17.3 Types of Joins

3.17.3.1 Visual Overview of Join Types

Below is a visual that depicts various types of merges/joins. Object x is the circle labeled as x. Object y is the circle labeled as y. The area of overlap in the Venn diagram indicates the rows on the keys that are shared between the two objects (e.g., the same player_id, season, and week). The non-overlapping area indicates the rows on the keys that are unique to each object. The shaded blue area indicates which rows (on the keys) are kept in the merged object from each of the two objects, when using each of the merge types. For instance, a left outer join keeps the shared rows and the rows that are unique to object x, but it drops the rows that are unique to object y.

Types of merges/joins

3.17.3.2 Full Outer Join

A full outer join includes all rows in x or y. It returns columns from x and y. Here is how to merge two data frames using a full outer join (i.e., “full join”):

Code
fullJoinData <- full_join(
  players,
  fantasyPoints,
  by = "ID")

fullJoinData
Code
dim(fullJoinData)
[1] 14 11

3.17.3.3 Left Outer Join

A left outer join includes all rows in x. It returns columns from x and y. Here is how to merge two data frames using a left outer join (“left join”):

Code
leftJoinData <- left_join(
  players,
  fantasyPoints,
  by = "ID")

leftJoinData
Code
dim(leftJoinData)
[1] 12 11

3.17.3.4 Right Outer Join

A right outer join includes all rows in y. It returns columns from x and y. Here is how to merge two data frames using a right outer join (“right join”):

Code
rightJoinData <- right_join(
  players,
  fantasyPoints,
  by = "ID")

rightJoinData
Code
dim(rightJoinData)
[1]  4 11

3.17.3.5 Inner Join

An inner join includes all rows that are in both x and y. An inner join will return one row of x for each matching row of y, and can duplicate values of records on either side (left or right) if x and y have more than one matching record. It returns columns from x and y. Here is how to merge two data frames using an inner join:

Code
innerJoinData <- inner_join(
  players,
  fantasyPoints,
  by = "ID")

innerJoinData
Code
dim(innerJoinData)
[1]  2 11

3.17.3.6 Semi Join

A semi join is a filter. A left semi join returns all rows from x with a match in y. That is, it filters out records from x that are not in y. Unlike an inner join, a left semi join will never duplicate rows of x, and it includes columns from only x (not from y). Here is how to merge two data frames using a left semi join:

Code
semiJoinData <- semi_join(
  players,
  fantasyPoints,
  by = "ID")

semiJoinData
Code
dim(semiJoinData)
[1]  2 10

3.17.3.7 Anti Join

An anti join is a filter. A left anti join returns all rows from x without a match in y. That is, it filters out records from x that are in y. It returns columns from only x (not from y). Here is how to merge two data frames using a left anti join:

Code
antiJoinData <- anti_join(
  players,
  fantasyPoints,
  by = "ID")

antiJoinData
Code
dim(antiJoinData)
[1] 10 10

3.17.3.8 Cross Join

A cross join combines each row in x with each row in y.

Code
crossJoinData <- cross_join(
  players,
  fantasyPoints)

crossJoinData
Code
dim(crossJoinData)
[1] 48 12

3.18 Transform Data from Long to Wide

Depending on the analysis, it may be important to restructure the data to be in long or wide form. When the data are in wide form, each player has only one row. When the data are in long form, each player has multiple rows—e.g., a row for each game. The data structure is called wide or long form because a dataset in wide form has more columns and fewer rows (i.e., it appears wider and shorter), whereas a dataset in long form has more rows and fewer columns (i.e., it appears narrower and taller).

Here are the data in the nfl_actualStats_offense_weekly object. The data are structured in “player-season-week form”. That is, every row in the dataset is uniquely identified by the variables, player_id, season, and week. This is an example of long form, because each player has multiple rows.

Original data:

Code
dataLong <- nfl_actualStats_offense_weekly %>% 
  select(player_id, player_display_name, season, week, fantasy_points)

dim(dataLong)
[1] 129739      5
Code
names(dataLong)
[1] "player_id"           "player_display_name" "season"             
[4] "week"                "fantasy_points"     

Below, we widen the data by two variables (season and week), using tidyverse, so that the data are now in “player form” (where each row is uniquely identified by the player_id variable):

Code
dataWide <- dataLong %>% 
  pivot_wider(
    names_from = c(season, week),
    names_glue = "{.value}_{season}_week{week}",
    values_from = fantasy_points)

dim(dataWide)
[1] 4021  530
Code
names(dataWide)
  [1] "player_id"                  "player_display_name"       
  [3] "fantasy_points_1999_week1"  "fantasy_points_1999_week2" 
  [5] "fantasy_points_1999_week4"  "fantasy_points_1999_week7" 
  [7] "fantasy_points_1999_week8"  "fantasy_points_1999_week9" 
  [9] "fantasy_points_1999_week10" "fantasy_points_1999_week11"
 [11] "fantasy_points_1999_week12" "fantasy_points_1999_week13"
 [13] "fantasy_points_1999_week14" "fantasy_points_1999_week15"
 [15] "fantasy_points_1999_week16" "fantasy_points_1999_week5" 
 [17] "fantasy_points_1999_week6"  "fantasy_points_1999_week17"
 [19] "fantasy_points_1999_week18" "fantasy_points_1999_week3" 
 [21] "fantasy_points_1999_week19" "fantasy_points_1999_week20"
 [23] "fantasy_points_1999_week21" "fantasy_points_2000_week1" 
 [25] "fantasy_points_2000_week12" "fantasy_points_2000_week14"
 [27] "fantasy_points_2000_week15" "fantasy_points_2000_week6" 
 [29] "fantasy_points_2000_week10" "fantasy_points_2000_week4" 
 [31] "fantasy_points_2000_week5"  "fantasy_points_2000_week7" 
 [33] "fantasy_points_2000_week8"  "fantasy_points_2000_week9" 
 [35] "fantasy_points_2000_week11" "fantasy_points_2000_week13"
 [37] "fantasy_points_2000_week2"  "fantasy_points_2000_week16"
 [39] "fantasy_points_2000_week17" "fantasy_points_2000_week3" 
 [41] "fantasy_points_2000_week18" "fantasy_points_2000_week19"
 [43] "fantasy_points_2000_week21" "fantasy_points_2000_week20"
 [45] "fantasy_points_2001_week15" "fantasy_points_2001_week17"
 [47] "fantasy_points_2001_week1"  "fantasy_points_2001_week3" 
 [49] "fantasy_points_2001_week4"  "fantasy_points_2001_week5" 
 [51] "fantasy_points_2001_week6"  "fantasy_points_2001_week9" 
 [53] "fantasy_points_2001_week11" "fantasy_points_2001_week12"
 [55] "fantasy_points_2001_week13" "fantasy_points_2001_week14"
 [57] "fantasy_points_2001_week16" "fantasy_points_2001_week2" 
 [59] "fantasy_points_2001_week7"  "fantasy_points_2001_week8" 
 [61] "fantasy_points_2001_week10" "fantasy_points_2001_week19"
 [63] "fantasy_points_2001_week18" "fantasy_points_2001_week20"
 [65] "fantasy_points_2001_week21" "fantasy_points_2002_week3" 
 [67] "fantasy_points_2002_week1"  "fantasy_points_2002_week2" 
 [69] "fantasy_points_2002_week4"  "fantasy_points_2002_week6" 
 [71] "fantasy_points_2002_week7"  "fantasy_points_2002_week8" 
 [73] "fantasy_points_2002_week9"  "fantasy_points_2002_week5" 
 [75] "fantasy_points_2002_week10" "fantasy_points_2002_week11"
 [77] "fantasy_points_2002_week12" "fantasy_points_2002_week13"
 [79] "fantasy_points_2002_week14" "fantasy_points_2002_week15"
 [81] "fantasy_points_2002_week16" "fantasy_points_2002_week17"
 [83] "fantasy_points_2002_week19" "fantasy_points_2002_week20"
 [85] "fantasy_points_2002_week21" "fantasy_points_2002_week18"
 [87] "fantasy_points_2003_week2"  "fantasy_points_2003_week4" 
 [89] "fantasy_points_2003_week5"  "fantasy_points_2003_week7" 
 [91] "fantasy_points_2003_week8"  "fantasy_points_2003_week9" 
 [93] "fantasy_points_2003_week10" "fantasy_points_2003_week11"
 [95] "fantasy_points_2003_week13" "fantasy_points_2003_week14"
 [97] "fantasy_points_2003_week15" "fantasy_points_2003_week17"
 [99] "fantasy_points_2003_week1"  "fantasy_points_2003_week3" 
[101] "fantasy_points_2003_week6"  "fantasy_points_2003_week12"
[103] "fantasy_points_2003_week16" "fantasy_points_2003_week18"
[105] "fantasy_points_2003_week19" "fantasy_points_2003_week20"
[107] "fantasy_points_2003_week21" "fantasy_points_2004_week2" 
[109] "fantasy_points_2004_week5"  "fantasy_points_2004_week6" 
[111] "fantasy_points_2004_week10" "fantasy_points_2004_week16"
[113] "fantasy_points_2004_week17" "fantasy_points_2004_week1" 
[115] "fantasy_points_2004_week3"  "fantasy_points_2004_week7" 
[117] "fantasy_points_2004_week8"  "fantasy_points_2004_week9" 
[119] "fantasy_points_2004_week11" "fantasy_points_2004_week12"
[121] "fantasy_points_2004_week13" "fantasy_points_2004_week14"
[123] "fantasy_points_2004_week15" "fantasy_points_2004_week4" 
[125] "fantasy_points_2004_week19" "fantasy_points_2004_week20"
[127] "fantasy_points_2004_week21" "fantasy_points_2004_week18"
[129] "fantasy_points_2005_week1"  "fantasy_points_2005_week2" 
[131] "fantasy_points_2005_week3"  "fantasy_points_2005_week4" 
[133] "fantasy_points_2005_week6"  "fantasy_points_2005_week7" 
[135] "fantasy_points_2005_week8"  "fantasy_points_2005_week10"
[137] "fantasy_points_2005_week11" "fantasy_points_2005_week13"
[139] "fantasy_points_2005_week14" "fantasy_points_2005_week15"
[141] "fantasy_points_2005_week16" "fantasy_points_2005_week17"
[143] "fantasy_points_2005_week5"  "fantasy_points_2005_week9" 
[145] "fantasy_points_2005_week12" "fantasy_points_2005_week18"
[147] "fantasy_points_2005_week19" "fantasy_points_2005_week20"
[149] "fantasy_points_2005_week21" "fantasy_points_2006_week4" 
[151] "fantasy_points_2006_week1"  "fantasy_points_2006_week2" 
[153] "fantasy_points_2006_week3"  "fantasy_points_2006_week10"
[155] "fantasy_points_2006_week11" "fantasy_points_2006_week12"
[157] "fantasy_points_2006_week13" "fantasy_points_2006_week14"
[159] "fantasy_points_2006_week5"  "fantasy_points_2006_week6" 
[161] "fantasy_points_2006_week7"  "fantasy_points_2006_week15"
[163] "fantasy_points_2006_week16" "fantasy_points_2006_week17"
[165] "fantasy_points_2006_week8"  "fantasy_points_2006_week9" 
[167] "fantasy_points_2006_week18" "fantasy_points_2006_week19"
[169] "fantasy_points_2006_week20" "fantasy_points_2006_week21"
[171] "fantasy_points_2007_week1"  "fantasy_points_2007_week2" 
[173] "fantasy_points_2007_week3"  "fantasy_points_2007_week5" 
[175] "fantasy_points_2007_week9"  "fantasy_points_2007_week16"
[177] "fantasy_points_2007_week17" "fantasy_points_2007_week14"
[179] "fantasy_points_2007_week4"  "fantasy_points_2007_week6" 
[181] "fantasy_points_2007_week7"  "fantasy_points_2007_week8" 
[183] "fantasy_points_2007_week10" "fantasy_points_2007_week11"
[185] "fantasy_points_2007_week12" "fantasy_points_2007_week13"
[187] "fantasy_points_2007_week15" "fantasy_points_2007_week19"
[189] "fantasy_points_2007_week21" "fantasy_points_2007_week18"
[191] "fantasy_points_2007_week20" "fantasy_points_2008_week8" 
[193] "fantasy_points_2008_week1"  "fantasy_points_2008_week2" 
[195] "fantasy_points_2008_week3"  "fantasy_points_2008_week4" 
[197] "fantasy_points_2008_week5"  "fantasy_points_2008_week6" 
[199] "fantasy_points_2008_week7"  "fantasy_points_2008_week14"
[201] "fantasy_points_2008_week10" "fantasy_points_2008_week11"
[203] "fantasy_points_2008_week12" "fantasy_points_2008_week13"
[205] "fantasy_points_2008_week15" "fantasy_points_2008_week16"
[207] "fantasy_points_2008_week17" "fantasy_points_2008_week9" 
[209] "fantasy_points_2008_week19" "fantasy_points_2008_week18"
[211] "fantasy_points_2008_week20" "fantasy_points_2008_week21"
[213] "fantasy_points_2009_week9"  "fantasy_points_2009_week11"
[215] "fantasy_points_2009_week2"  "fantasy_points_2009_week3" 
[217] "fantasy_points_2009_week5"  "fantasy_points_2009_week7" 
[219] "fantasy_points_2009_week12" "fantasy_points_2009_week13"
[221] "fantasy_points_2009_week14" "fantasy_points_2009_week15"
[223] "fantasy_points_2009_week16" "fantasy_points_2009_week17"
[225] "fantasy_points_2009_week1"  "fantasy_points_2009_week4" 
[227] "fantasy_points_2009_week8"  "fantasy_points_2009_week6" 
[229] "fantasy_points_2009_week10" "fantasy_points_2009_week18"
[231] "fantasy_points_2009_week19" "fantasy_points_2009_week20"
[233] "fantasy_points_2009_week21" "fantasy_points_2010_week2" 
[235] "fantasy_points_2010_week3"  "fantasy_points_2010_week4" 
[237] "fantasy_points_2010_week1"  "fantasy_points_2010_week7" 
[239] "fantasy_points_2010_week17" "fantasy_points_2010_week6" 
[241] "fantasy_points_2010_week8"  "fantasy_points_2010_week10"
[243] "fantasy_points_2010_week13" "fantasy_points_2010_week14"
[245] "fantasy_points_2010_week15" "fantasy_points_2010_week16"
[247] "fantasy_points_2010_week5"  "fantasy_points_2010_week20"
[249] "fantasy_points_2010_week12" "fantasy_points_2010_week11"
[251] "fantasy_points_2010_week18" "fantasy_points_2010_week19"
[253] "fantasy_points_2010_week21" "fantasy_points_2010_week9" 
[255] "fantasy_points_2011_week17" "fantasy_points_2011_week13"
[257] "fantasy_points_2011_week14" "fantasy_points_2011_week16"
[259] "fantasy_points_2011_week15" "fantasy_points_2011_week1" 
[261] "fantasy_points_2011_week2"  "fantasy_points_2011_week3" 
[263] "fantasy_points_2011_week4"  "fantasy_points_2011_week5" 
[265] "fantasy_points_2011_week6"  "fantasy_points_2011_week7" 
[267] "fantasy_points_2011_week9"  "fantasy_points_2011_week10"
[269] "fantasy_points_2011_week11" "fantasy_points_2011_week12"
[271] "fantasy_points_2011_week19" "fantasy_points_2011_week8" 
[273] "fantasy_points_2011_week18" "fantasy_points_2011_week20"
[275] "fantasy_points_2011_week21" "fantasy_points_2012_week12"
[277] "fantasy_points_2012_week13" "fantasy_points_2012_week2" 
[279] "fantasy_points_2012_week3"  "fantasy_points_2012_week4" 
[281] "fantasy_points_2012_week5"  "fantasy_points_2012_week7" 
[283] "fantasy_points_2012_week8"  "fantasy_points_2012_week9" 
[285] "fantasy_points_2012_week11" "fantasy_points_2012_week16"
[287] "fantasy_points_2012_week1"  "fantasy_points_2012_week6" 
[289] "fantasy_points_2012_week10" "fantasy_points_2012_week14"
[291] "fantasy_points_2012_week15" "fantasy_points_2012_week17"
[293] "fantasy_points_2012_week19" "fantasy_points_2012_week20"
[295] "fantasy_points_2012_week21" "fantasy_points_2012_week18"
[297] "fantasy_points_2013_week1"  "fantasy_points_2013_week2" 
[299] "fantasy_points_2013_week3"  "fantasy_points_2013_week4" 
[301] "fantasy_points_2013_week5"  "fantasy_points_2013_week7" 
[303] "fantasy_points_2013_week8"  "fantasy_points_2013_week9" 
[305] "fantasy_points_2013_week10" "fantasy_points_2013_week11"
[307] "fantasy_points_2013_week12" "fantasy_points_2013_week13"
[309] "fantasy_points_2013_week14" "fantasy_points_2013_week15"
[311] "fantasy_points_2013_week16" "fantasy_points_2013_week17"
[313] "fantasy_points_2013_week6"  "fantasy_points_2013_week19"
[315] "fantasy_points_2013_week20" "fantasy_points_2013_week21"
[317] "fantasy_points_2013_week18" "fantasy_points_2014_week3" 
[319] "fantasy_points_2014_week4"  "fantasy_points_2014_week16"
[321] "fantasy_points_2014_week17" "fantasy_points_2014_week1" 
[323] "fantasy_points_2014_week2"  "fantasy_points_2014_week5" 
[325] "fantasy_points_2014_week6"  "fantasy_points_2014_week7" 
[327] "fantasy_points_2014_week8"  "fantasy_points_2014_week9" 
[329] "fantasy_points_2014_week10" "fantasy_points_2014_week11"
[331] "fantasy_points_2014_week12" "fantasy_points_2014_week13"
[333] "fantasy_points_2014_week14" "fantasy_points_2014_week15"
[335] "fantasy_points_2014_week19" "fantasy_points_2014_week20"
[337] "fantasy_points_2014_week21" "fantasy_points_2014_week18"
[339] "fantasy_points_2015_week4"  "fantasy_points_2015_week5" 
[341] "fantasy_points_2015_week11" "fantasy_points_2015_week12"
[343] "fantasy_points_2015_week13" "fantasy_points_2015_week14"
[345] "fantasy_points_2015_week15" "fantasy_points_2015_week16"
[347] "fantasy_points_2015_week1"  "fantasy_points_2015_week2" 
[349] "fantasy_points_2015_week3"  "fantasy_points_2015_week6" 
[351] "fantasy_points_2015_week8"  "fantasy_points_2015_week9" 
[353] "fantasy_points_2015_week10" "fantasy_points_2015_week17"
[355] "fantasy_points_2015_week19" "fantasy_points_2015_week20"
[357] "fantasy_points_2015_week21" "fantasy_points_2015_week7" 
[359] "fantasy_points_2015_week18" "fantasy_points_2016_week5" 
[361] "fantasy_points_2016_week6"  "fantasy_points_2016_week7" 
[363] "fantasy_points_2016_week8"  "fantasy_points_2016_week10"
[365] "fantasy_points_2016_week11" "fantasy_points_2016_week12"
[367] "fantasy_points_2016_week13" "fantasy_points_2016_week14"
[369] "fantasy_points_2016_week15" "fantasy_points_2016_week16"
[371] "fantasy_points_2016_week17" "fantasy_points_2016_week19"
[373] "fantasy_points_2016_week20" "fantasy_points_2016_week21"
[375] "fantasy_points_2016_week1"  "fantasy_points_2016_week2" 
[377] "fantasy_points_2016_week3"  "fantasy_points_2016_week4" 
[379] "fantasy_points_2016_week9"  "fantasy_points_2016_week18"
[381] "fantasy_points_2017_week1"  "fantasy_points_2017_week2" 
[383] "fantasy_points_2017_week3"  "fantasy_points_2017_week4" 
[385] "fantasy_points_2017_week5"  "fantasy_points_2017_week6" 
[387] "fantasy_points_2017_week7"  "fantasy_points_2017_week8" 
[389] "fantasy_points_2017_week10" "fantasy_points_2017_week11"
[391] "fantasy_points_2017_week12" "fantasy_points_2017_week13"
[393] "fantasy_points_2017_week14" "fantasy_points_2017_week15"
[395] "fantasy_points_2017_week16" "fantasy_points_2017_week17"
[397] "fantasy_points_2017_week19" "fantasy_points_2017_week20"
[399] "fantasy_points_2017_week21" "fantasy_points_2017_week9" 
[401] "fantasy_points_2017_week18" "fantasy_points_2018_week1" 
[403] "fantasy_points_2018_week2"  "fantasy_points_2018_week3" 
[405] "fantasy_points_2018_week4"  "fantasy_points_2018_week5" 
[407] "fantasy_points_2018_week6"  "fantasy_points_2018_week7" 
[409] "fantasy_points_2018_week8"  "fantasy_points_2018_week9" 
[411] "fantasy_points_2018_week10" "fantasy_points_2018_week12"
[413] "fantasy_points_2018_week13" "fantasy_points_2018_week14"
[415] "fantasy_points_2018_week15" "fantasy_points_2018_week16"
[417] "fantasy_points_2018_week17" "fantasy_points_2018_week19"
[419] "fantasy_points_2018_week20" "fantasy_points_2018_week21"
[421] "fantasy_points_2018_week11" "fantasy_points_2018_week18"
[423] "fantasy_points_2019_week1"  "fantasy_points_2019_week2" 
[425] "fantasy_points_2019_week3"  "fantasy_points_2019_week4" 
[427] "fantasy_points_2019_week5"  "fantasy_points_2019_week6" 
[429] "fantasy_points_2019_week7"  "fantasy_points_2019_week8" 
[431] "fantasy_points_2019_week9"  "fantasy_points_2019_week11"
[433] "fantasy_points_2019_week12" "fantasy_points_2019_week13"
[435] "fantasy_points_2019_week14" "fantasy_points_2019_week15"
[437] "fantasy_points_2019_week16" "fantasy_points_2019_week17"
[439] "fantasy_points_2019_week18" "fantasy_points_2019_week10"
[441] "fantasy_points_2019_week19" "fantasy_points_2019_week20"
[443] "fantasy_points_2019_week21" "fantasy_points_2020_week1" 
[445] "fantasy_points_2020_week2"  "fantasy_points_2020_week3" 
[447] "fantasy_points_2020_week4"  "fantasy_points_2020_week5" 
[449] "fantasy_points_2020_week6"  "fantasy_points_2020_week7" 
[451] "fantasy_points_2020_week8"  "fantasy_points_2020_week9" 
[453] "fantasy_points_2020_week10" "fantasy_points_2020_week11"
[455] "fantasy_points_2020_week12" "fantasy_points_2020_week14"
[457] "fantasy_points_2020_week15" "fantasy_points_2020_week16"
[459] "fantasy_points_2020_week17" "fantasy_points_2020_week18"
[461] "fantasy_points_2020_week19" "fantasy_points_2020_week20"
[463] "fantasy_points_2020_week21" "fantasy_points_2020_week13"
[465] "fantasy_points_2021_week1"  "fantasy_points_2021_week2" 
[467] "fantasy_points_2021_week3"  "fantasy_points_2021_week4" 
[469] "fantasy_points_2021_week5"  "fantasy_points_2021_week6" 
[471] "fantasy_points_2021_week7"  "fantasy_points_2021_week8" 
[473] "fantasy_points_2021_week10" "fantasy_points_2021_week11"
[475] "fantasy_points_2021_week12" "fantasy_points_2021_week13"
[477] "fantasy_points_2021_week14" "fantasy_points_2021_week15"
[479] "fantasy_points_2021_week16" "fantasy_points_2021_week17"
[481] "fantasy_points_2021_week18" "fantasy_points_2021_week19"
[483] "fantasy_points_2021_week20" "fantasy_points_2021_week9" 
[485] "fantasy_points_2021_week21" "fantasy_points_2021_week22"
[487] "fantasy_points_2022_week1"  "fantasy_points_2022_week2" 
[489] "fantasy_points_2022_week3"  "fantasy_points_2022_week4" 
[491] "fantasy_points_2022_week5"  "fantasy_points_2022_week6" 
[493] "fantasy_points_2022_week7"  "fantasy_points_2022_week8" 
[495] "fantasy_points_2022_week9"  "fantasy_points_2022_week10"
[497] "fantasy_points_2022_week12" "fantasy_points_2022_week13"
[499] "fantasy_points_2022_week14" "fantasy_points_2022_week15"
[501] "fantasy_points_2022_week16" "fantasy_points_2022_week17"
[503] "fantasy_points_2022_week18" "fantasy_points_2022_week19"
[505] "fantasy_points_2022_week11" "fantasy_points_2022_week20"
[507] "fantasy_points_2022_week21" "fantasy_points_2022_week22"
[509] "fantasy_points_2023_week1"  "fantasy_points_2023_week4" 
[511] "fantasy_points_2023_week7"  "fantasy_points_2023_week11"
[513] "fantasy_points_2023_week14" "fantasy_points_2023_week16"
[515] "fantasy_points_2023_week13" "fantasy_points_2023_week15"
[517] "fantasy_points_2023_week17" "fantasy_points_2023_week19"
[519] "fantasy_points_2023_week2"  "fantasy_points_2023_week3" 
[521] "fantasy_points_2023_week5"  "fantasy_points_2023_week6" 
[523] "fantasy_points_2023_week8"  "fantasy_points_2023_week12"
[525] "fantasy_points_2023_week18" "fantasy_points_2023_week10"
[527] "fantasy_points_2023_week21" "fantasy_points_2023_week22"
[529] "fantasy_points_2023_week9"  "fantasy_points_2023_week20"

3.19 Transform Data from Wide to Long

Conversely, we can also restructure data from wide to long.

Original data:

Code
dataWide <- nfl_actualStats_offense_weekly %>% 
  select(player_id, player_display_name, season, week, recent_team, opponent_team)

dim(dataWide)
[1] 129739      6
Code
names(dataWide)
[1] "player_id"           "player_display_name" "season"             
[4] "week"                "recent_team"         "opponent_team"      

Data in long form, transformed from wide form using tidyverse:

Code
dataLong <- dataWide %>% 
  pivot_longer(
    cols = c(recent_team, opponent_team),
    names_to = "role",
    values_to = "team")

dim(dataLong)
[1] 259478      6
Code
names(dataLong)
[1] "player_id"           "player_display_name" "season"             
[4] "week"                "role"                "team"               

3.20 Loops

If you want to perform the same computation multiple times, it can be faster to do it in a loop compared to writing out the same computation many times. For instance, here is a loop that runs from 1 to 12 (the number of players in the players object), incrementing by 1 after each iteration. The loop prints each element of a vector (i.e., the player’s name) and the loop index (i) that indicates where the loop is in terms of its iterations:

Code
for(i in 1:length(players$ID)){
  print(paste("The loop is at index:", i, sep = " "))
  print(paste("My favorite player is:", players$name[i], sep = " "))
}
[1] "The loop is at index: 1"
[1] "My favorite player is: Ken Cussion"
[1] "The loop is at index: 2"
[1] "My favorite player is: Ben Sacked"
[1] "The loop is at index: 3"
[1] "My favorite player is: Chuck Downfield"
[1] "The loop is at index: 4"
[1] "My favorite player is: Ron Ingback"
[1] "The loop is at index: 5"
[1] "My favorite player is: Rhonda Ball"
[1] "The loop is at index: 6"
[1] "My favorite player is: Hugo Long"
[1] "The loop is at index: 7"
[1] "My favorite player is: Lionel Scrimmage"
[1] "The loop is at index: 8"
[1] "My favorite player is: Drew Blood"
[1] "The loop is at index: 9"
[1] "My favorite player is: Chase Emdown"
[1] "The loop is at index: 10"
[1] "My favorite player is: Justin Time"
[1] "The loop is at index: 11"
[1] "My favorite player is: Spike D'Ball"
[1] "The loop is at index: 12"
[1] "My favorite player is: Isac Ulooz"

3.21 Calculations

3.21.1 Historical Actual Player Statistics

In addition to week-by-week actual player statistics, we can also compute historical actual player statistics as a function of different timeframes, including season-by-season and career statistics.

3.21.1.1 Career Statistics

First, we can compute the players’ career statistics using the calculate_player_stats(), calculate_player_stats_def(), and calculate_player_stats_kicking() functions from the nflfastR package for offensive players, defensive players, and kickers, respectively.

Note 3.4: Calculating players’ career statistics

Note: the following code takes a while to run.

Code
nfl_actualStats_offense_career <- nflfastR::calculate_player_stats(
  nfl_pbp,
  weekly = FALSE)

nfl_actualStats_defense_career <- nflfastR::calculate_player_stats_def(
  nfl_pbp,
  weekly = FALSE)

nfl_actualStats_kicking_career <- nflfastR::calculate_player_stats_kicking(
  nfl_pbp,
  weekly = FALSE)

3.21.1.2 Season-by-Season Statistics

Second, we can compute the players’ season-by-season statistics.

Code
seasons <- unique(nfl_pbp$season)

nfl_pbp_seasonalList <- list()
nfl_actualStats_offense_seasonalList <- list()
nfl_actualStats_defense_seasonalList <- list()
nfl_actualStats_kicking_seasonalList <- list()
Note 3.5: Calculating players’ season-by-season statistics

Note: the following code takes a while to run.

Code
pb <- txtProgressBar(
  min = 0,
  max = length(seasons),
  style = 3)

for(i in 1:length(seasons)){
  # Subset play-by-play data by season
  nfl_pbp_seasonalList[[i]] <- nfl_pbp %>% 
    dplyr::filter(season == seasons[i])
  
  # Compute actual statistics by season
  nfl_actualStats_offense_seasonalList[[i]] <- 
    nflfastR::calculate_player_stats(
      nfl_pbp_seasonalList[[i]],
      weekly = FALSE)

  nfl_actualStats_defense_seasonalList[[i]] <- 
    nflfastR::calculate_player_stats_def(
      nfl_pbp_seasonalList[[i]],
      weekly = FALSE)

  nfl_actualStats_kicking_seasonalList[[i]] <- 
    nflfastR::calculate_player_stats_kicking(
      nfl_pbp_seasonalList[[i]],
      weekly = FALSE)

  nfl_actualStats_offense_seasonalList[[i]]$season <- seasons[i]
  nfl_actualStats_defense_seasonalList[[i]]$season <- seasons[i]
  nfl_actualStats_kicking_seasonalList[[i]]$season <- seasons[i]
  
  print(
    paste("Completed computing projections for season: ", seasons[i], sep = ""))

  # Update the progress bar
  setTxtProgressBar(pb, i)
}

# Close the progress bar
close(pb)

nfl_actualStats_offense_seasonal <- nfl_actualStats_offense_seasonalList %>% 
  dplyr::bind_rows()
nfl_actualStats_defense_seasonal <- nfl_actualStats_defense_seasonalList %>% 
  dplyr::bind_rows()
nfl_actualStats_kicking_seasonal <- nfl_actualStats_kicking_seasonalList %>% 
  dplyr::bind_rows()

3.21.1.3 Week-by-Week Statistics

We already load players’ week-by-week statistics above. Nevertheless, we could compute players’ weekly statistics from the play-by-play data using the following syntax:

Code
nfl_actualStats_offense_weekly <- nflfastR::calculate_player_stats(
  nfl_pbp,
  weekly = TRUE)

nfl_actualStats_defense_weekly <- nflfastR::calculate_player_stats_def(
  nfl_pbp,
  weekly = TRUE)

nfl_actualStats_kicking_weekly <- nflfastR::calculate_player_stats_kicking(
  nfl_pbp,
  weekly = TRUE)

3.21.2 Historical Actual Fantasy Points

Specify scoring settings:

3.21.2.1 Weekly

3.21.2.2 Seasonal

3.21.2.3 Career

3.21.3 Player Age and Experience

3.21.3.1 Weekly

Code
# Reshape from wide to long format
nfl_actualStats_offense_weekly_long <- nfl_actualStats_offense_weekly %>% 
  tidyr::pivot_longer(
    cols = c(recent_team, opponent_team),
    names_to = "role",
    values_to = "team")

# Perform separate inner join operations for the home_team and away_team
nfl_actualStats_offense_weekly_home <- dplyr::inner_join(
  nfl_actualStats_offense_weekly_long,
  nfl_schedules,
  by = c("season","week","team" = "home_team")) %>% 
  mutate(home_away = "home_team")

nfl_actualStats_offense_weekly_away <- dplyr::inner_join(
  nfl_actualStats_offense_weekly_long,
  nfl_schedules,
  by = c("season","week","team" = "away_team")) %>% 
  mutate(home_away = "away_team")

nfl_actualStats_defense_weekly_home <- dplyr::inner_join(
  nfl_actualStats_defense_weekly,
  nfl_schedules,
  by = c("season","week","team" = "home_team")) %>% 
  mutate(home_away = "home_team")

nfl_actualStats_defense_weekly_away <- dplyr::inner_join(
  nfl_actualStats_defense_weekly,
  nfl_schedules,
  by = c("season","week","team" = "away_team")) %>% 
  mutate(home_away = "away_team")

nfl_actualStats_kicking_weekly_home <- dplyr::inner_join(
  nfl_actualStats_kicking_weekly,
  nfl_schedules,
  by = c("season","week","team" = "home_team")) %>% 
  mutate(home_away = "home_team")

nfl_actualStats_kicking_weekly_away <- dplyr::inner_join(
  nfl_actualStats_kicking_weekly,
  nfl_schedules,
  by = c("season","week","team" = "away_team")) %>% 
  mutate(home_away = "away_team")

# Combine the results of the join operations
nfl_actualStats_offense_weekly_schedules_long <- dplyr::bind_rows(
  nfl_actualStats_offense_weekly_home,
  nfl_actualStats_offense_weekly_away)

nfl_actualStats_defense_weekly_schedules_long <- dplyr::bind_rows(
  nfl_actualStats_defense_weekly_home,
  nfl_actualStats_defense_weekly_away)

nfl_actualStats_kicking_weekly_schedules_long <- dplyr::bind_rows(
  nfl_actualStats_kicking_weekly_home,
  nfl_actualStats_kicking_weekly_away)

# Reshape from long to wide
player_game_gameday_offense <- nfl_actualStats_offense_weekly_schedules_long %>%
  dplyr::distinct(player_id, season, week, game_id, home_away, team, gameday) %>% #, .keep_all = TRUE
  tidyr::pivot_wider(
    names_from = home_away,
    values_from = team)

player_game_gameday_defense <- nfl_actualStats_defense_weekly_schedules_long %>%
  dplyr::distinct(player_id, season, week, game_id, home_away, team, gameday) %>% #, .keep_all = TRUE
  tidyr::pivot_wider(
    names_from = home_away,
    values_from = team)

player_game_gameday_kicking <- nfl_actualStats_kicking_weekly_schedules_long %>%
  dplyr::distinct(player_id, season, week, game_id, home_away, team, gameday) %>% #, .keep_all = TRUE
  tidyr::pivot_wider(
    names_from = home_away,
    values_from = team)

# Merge player birthdate and the game date
player_game_birthdate_gameday_offense <- dplyr::left_join(
  player_game_gameday_offense,
  unique(nfl_players[,c("gsis_id","birth_date")]),
  by = c("player_id" = "gsis_id")
)

player_game_birthdate_gameday_defense <- dplyr::left_join(
  player_game_gameday_defense,
  unique(nfl_players[,c("gsis_id","birth_date")]),
  by = c("player_id" = "gsis_id")
)

player_game_birthdate_gameday_kicking <- dplyr::left_join(
  player_game_gameday_kicking,
  unique(nfl_players[,c("gsis_id","birth_date")]),
  by = c("player_id" = "gsis_id")
)

player_game_birthdate_gameday_offense$birth_date <- lubridate::ymd(player_game_birthdate_gameday_offense$birth_date)
player_game_birthdate_gameday_offense$gameday <- lubridate::ymd(player_game_birthdate_gameday_offense$gameday)

player_game_birthdate_gameday_defense$birth_date <- lubridate::ymd(player_game_birthdate_gameday_defense$birth_date)
player_game_birthdate_gameday_defense$gameday <- lubridate::ymd(player_game_birthdate_gameday_defense$gameday)

player_game_birthdate_gameday_kicking$birth_date <- lubridate::ymd(player_game_birthdate_gameday_kicking$birth_date)
player_game_birthdate_gameday_kicking$gameday <- lubridate::ymd(player_game_birthdate_gameday_kicking$gameday)

# Calculate player's age for a given week as the difference between their birthdate and the game date
player_game_birthdate_gameday_offense$age <- lubridate::interval(
  start = player_game_birthdate_gameday_offense$birth_date,
  end = player_game_birthdate_gameday_offense$gameday
) %>% 
  lubridate::time_length(unit = "years")

player_game_birthdate_gameday_defense$age <- lubridate::interval(
  start = player_game_birthdate_gameday_defense$birth_date,
  end = player_game_birthdate_gameday_defense$gameday
) %>% 
  lubridate::time_length(unit = "years")

player_game_birthdate_gameday_kicking$age <- lubridate::interval(
  start = player_game_birthdate_gameday_kicking$birth_date,
  end = player_game_birthdate_gameday_kicking$gameday
) %>% 
  lubridate::time_length(unit = "years")

# Merge with Pro Football Reference Data on Player Age by Season
player_game_birthdate_gameday_offense <- player_game_birthdate_gameday_offense %>% 
  dplyr::left_join(
    nfl_advancedStatsPFR_seasonal %>% filter(!is.na(gsis_id), !is.na(season), !is.na(age)) %>% select(gsis_id, season, age) %>% unique(),
    by = c("player_id" = "gsis_id", "season")
  )

player_game_birthdate_gameday_defense <- player_game_birthdate_gameday_defense %>% 
  dplyr::left_join(
    nfl_advancedStatsPFR_seasonal %>% filter(!is.na(gsis_id), !is.na(season), !is.na(age)) %>% select(gsis_id, season, age) %>% unique(),
    by = c("player_id" = "gsis_id", "season")
  )

player_game_birthdate_gameday_kicking <- player_game_birthdate_gameday_kicking %>% 
  dplyr::left_join(
    nfl_advancedStatsPFR_seasonal %>% filter(!is.na(gsis_id), !is.na(season), !is.na(age)) %>% select(gsis_id, season, age) %>% unique(),
    by = c("player_id" = "gsis_id", "season")
  )

# Set age as first non-missing value from calculation above or from PFR
player_game_birthdate_gameday_offense <- player_game_birthdate_gameday_offense %>% 
  mutate(age = coalesce(age.x, age.y)) %>% 
  select(-age.x, -age.y)

player_game_birthdate_gameday_defense <- player_game_birthdate_gameday_defense %>% 
  mutate(age = coalesce(age.x, age.y)) %>% 
  select(-age.x, -age.y)

player_game_birthdate_gameday_kicking <- player_game_birthdate_gameday_kicking %>% 
  mutate(age = coalesce(age.x, age.y)) %>% 
  select(-age.x, -age.y)

# Calculate ageCentered and ageCenteredQuadratic
player_game_birthdate_gameday_offense$ageCentered20 <- player_game_birthdate_gameday_offense$age - 20
player_game_birthdate_gameday_offense$ageCentered20Quadratic <- player_game_birthdate_gameday_offense$ageCentered20 ^ 2

player_game_birthdate_gameday_defense$ageCentered20 <- player_game_birthdate_gameday_defense$age - 20
player_game_birthdate_gameday_defense$ageCentered20Quadratic <- player_game_birthdate_gameday_defense$ageCentered20 ^ 2

player_game_birthdate_gameday_kicking$ageCentered20 <- player_game_birthdate_gameday_kicking$age - 20
player_game_birthdate_gameday_kicking$ageCentered20Quadratic <- player_game_birthdate_gameday_kicking$ageCentered20 ^ 2

# Merge with player info
player_age_offense <- dplyr::left_join(
  player_game_birthdate_gameday_offense,
  nfl_players %>% select(-birth_date, -season),
  by = c("player_id" = "gsis_id"))

player_age_defense <- dplyr::left_join(
  player_game_birthdate_gameday_defense,
  nfl_players %>% select(-birth_date, -season),
  by = c("player_id" = "gsis_id"))

player_age_kicking <- dplyr::left_join(
  player_game_birthdate_gameday_kicking,
  nfl_players %>% select(-birth_date, -season),
  by = c("player_id" = "gsis_id"))

# Add game_id to weekly stats to facilitate merging
nfl_actualStats_game_offense_weekly <- nfl_actualStats_offense_weekly %>% 
  dplyr::left_join(
    player_age_offense[,c("season","week","player_id","game_id")],
    by = c("season","week","player_id"))

nfl_actualStats_game_defense_weekly <- nfl_actualStats_defense_weekly %>% 
  dplyr::left_join(
    player_age_offense[,c("season","week","player_id","game_id")],
    by = c("season","week","player_id"))

nfl_actualStats_game_kicking_weekly <- nfl_actualStats_kicking_weekly %>% 
  dplyr::left_join(
    player_age_offense[,c("season","week","player_id","game_id")],
    by = c("season","week","player_id"))

# Merge with player weekly stats
player_stats_weekly_offense <- dplyr::full_join(
  player_age_offense %>% select(-position, -position_group),
  nfl_actualStats_game_offense_weekly,
  by = c("season","week","player_id","game_id"))

player_stats_weekly_defense <- dplyr::full_join(
  player_age_defense %>% select(-position, -position_group),
  nfl_actualStats_game_defense_weekly,
  by = c("season","week","player_id","game_id"))

player_stats_weekly_kicking <- dplyr::full_join(
  player_age_kicking %>% select(-position, -position_group),
  nfl_actualStats_game_kicking_weekly,
  by = c("season","week","player_id","game_id"))

player_stats_weekly_offense$total_years_of_experience <- as.integer(player_stats_weekly_offense$years_of_experience)
player_stats_weekly_defense$total_years_of_experience <- as.integer(player_stats_weekly_defense$years_of_experience)
player_stats_weekly_kicking$total_years_of_experience <- as.integer(player_stats_weekly_kicking$years_of_experience)

player_stats_weekly_offense$years_of_experience <- NULL
player_stats_weekly_defense$years_of_experience <- NULL
player_stats_weekly_kicking$years_of_experience <- NULL

distinct_seasons_offense <- player_stats_weekly_offense %>%
  dplyr::select(player_id, season) %>%
  dplyr::distinct() %>% 
  dplyr::left_join(
    nfl_players[,c("gsis_id","years_of_experience")],
    by = c("player_id" = "gsis_id")
  ) %>% 
  dplyr::mutate(total_years_of_experience = as.integer(years_of_experience)) %>% 
  dplyr::select(-years_of_experience)

distinct_seasons_defense <- player_stats_weekly_defense %>%
  dplyr::select(player_id, season) %>%
  dplyr::distinct() %>% 
  dplyr::left_join(
    nfl_players[,c("gsis_id","years_of_experience")],
    by = c("player_id" = "gsis_id")
  ) %>% 
  dplyr::mutate(total_years_of_experience = as.integer(years_of_experience)) %>% 
  dplyr::select(-years_of_experience)

distinct_seasons_kicking <- player_stats_weekly_kicking %>%
  dplyr::select(player_id, season) %>%
  dplyr::distinct() %>% 
  dplyr::left_join(
    nfl_players[,c("gsis_id","years_of_experience")],
    by = c("player_id" = "gsis_id")
  ) %>% 
  dplyr::mutate(total_years_of_experience = as.integer(years_of_experience)) %>% 
  dplyr::select(-years_of_experience)

years_of_experience_offense <- distinct_seasons_offense %>% 
  dplyr::arrange(player_id, -season) %>% 
  dplyr::group_by(player_id) %>%
  dplyr::mutate(years_of_experience = first(total_years_of_experience) - (row_number() - 1)) %>%
  dplyr::ungroup()

years_of_experience_defense <- distinct_seasons_defense %>% 
  dplyr::arrange(player_id, -season) %>% 
  dplyr::group_by(player_id) %>%
  dplyr::mutate(years_of_experience = first(total_years_of_experience) - (row_number() - 1)) %>%
  dplyr::ungroup()

years_of_experience_kicking <- distinct_seasons_kicking %>% 
  dplyr::arrange(player_id, -season) %>% 
  dplyr::group_by(player_id) %>%
  dplyr::mutate(years_of_experience = first(total_years_of_experience) - (row_number() - 1)) %>%
  dplyr::ungroup()

years_of_experience_offense$years_of_experience[which(years_of_experience_offense$years_of_experience < 0)] <- 0
years_of_experience_defense$years_of_experience[which(years_of_experience_defense$years_of_experience < 0)] <- 0
years_of_experience_kicking$years_of_experience[which(years_of_experience_kicking$years_of_experience < 0)] <- 0

player_stats_weekly_offense <- player_stats_weekly_offense %>% 
  dplyr::left_join(
    years_of_experience_offense[,c("player_id","season","years_of_experience")],
    by = c("player_id","season")
  )

player_stats_weekly_defense <- player_stats_weekly_defense %>% 
  dplyr::left_join(
    years_of_experience_offense[,c("player_id","season","years_of_experience")],
    by = c("player_id","season")
  )

player_stats_weekly_kicking <- player_stats_weekly_kicking %>% 
  dplyr::left_join(
    years_of_experience_offense[,c("player_id","season","years_of_experience")],
    by = c("player_id","season")
  )

# Arrange data
player_stats_weekly_offense <- player_stats_weekly_offense %>% 
  arrange(player_display_name, season, week)

player_stats_weekly_defense <- player_stats_weekly_defense %>% 
  arrange(player_display_name, season, week)

player_stats_weekly_kicking <- player_stats_weekly_kicking %>% 
  arrange(player_display_name, season, week)
Code
# Save data
save(
  player_stats_weekly_offense, player_stats_weekly_defense, player_stats_weekly_kicking,
  file = "./data/player_stats_weekly.RData"
)

3.21.3.2 Seasonal

Code
# Merge player info with seasonal stats
player_stats_seasonal_offense <- dplyr::full_join(
  nfl_actualStats_offense_seasonal,
  nfl_players %>% select(-position, -position_group, -season),
  by = c("player_id" = "gsis_id")
)

player_stats_seasonal_defense <- dplyr::full_join(
  nfl_actualStats_defense_seasonal,
  nfl_players %>% select(-position, -position_group, -season),
  by = c("player_id" = "gsis_id")
)

player_stats_seasonal_kicking <- dplyr::full_join(
  nfl_actualStats_kicking_seasonal,
  nfl_players %>% select(-position, -position_group, -season),
  by = c("player_id" = "gsis_id")
)

# Calculate age
season_startdate <- nfl_schedules %>% 
  dplyr::group_by(season) %>% 
  dplyr::summarise(startdate = min(gameday, na.rm = TRUE))

player_stats_seasonal_offense <- player_stats_seasonal_offense %>% 
  dplyr::left_join(
    season_startdate,
    by = "season"
  )

player_stats_seasonal_defense <- player_stats_seasonal_defense %>% 
  dplyr::left_join(
    season_startdate,
    by = "season"
  )

player_stats_seasonal_kicking <- player_stats_seasonal_kicking %>% 
  dplyr::left_join(
    season_startdate,
    by = "season"
  )

player_stats_seasonal_offense$age <- lubridate::interval(
  start = player_stats_seasonal_offense$birth_date,
  end = player_stats_seasonal_offense$startdate
) %>% 
  lubridate::time_length(unit = "years")

player_stats_seasonal_defense$age <- lubridate::interval(
  start = player_stats_seasonal_defense$birth_date,
  end = player_stats_seasonal_defense$startdate
) %>% 
  lubridate::time_length(unit = "years")

player_stats_seasonal_kicking$age <- lubridate::interval(
  start = player_stats_seasonal_kicking$birth_date,
  end = player_stats_seasonal_kicking$startdate
) %>% 
  lubridate::time_length(unit = "years")

# Merge with Pro Football Reference Data on Player Age by Season
player_stats_seasonal_offense <- player_stats_seasonal_offense %>% 
  dplyr::left_join(
    nfl_advancedStatsPFR_seasonal %>% filter(!is.na(gsis_id), !is.na(season), !is.na(age)) %>% select(gsis_id, season, age) %>% unique(),
    by = c("player_id" = "gsis_id", "season")
  )

player_stats_seasonal_defense <- player_stats_seasonal_defense %>% 
  dplyr::left_join(
    nfl_advancedStatsPFR_seasonal %>% filter(!is.na(gsis_id), !is.na(season), !is.na(age)) %>% select(gsis_id, season, age) %>% unique(),
    by = c("player_id" = "gsis_id", "season")
  )

player_stats_seasonal_kicking <- player_stats_seasonal_kicking %>% 
  dplyr::left_join(
    nfl_advancedStatsPFR_seasonal %>% filter(!is.na(gsis_id), !is.na(season), !is.na(age)) %>% select(gsis_id, season, age) %>% unique(),
    by = c("player_id" = "gsis_id", "season")
  )

# Set age as first non-missing value from calculation above or from PFR
player_stats_seasonal_offense <- player_stats_seasonal_offense %>% 
  mutate(age = coalesce(age.x, age.y)) %>% 
  select(-age.x, -age.y)

player_stats_seasonal_defense <- player_stats_seasonal_defense %>% 
  mutate(age = coalesce(age.x, age.y)) %>% 
  select(-age.x, -age.y)

player_stats_seasonal_kicking <- player_stats_seasonal_kicking %>% 
  mutate(age = coalesce(age.x, age.y)) %>% 
  select(-age.x, -age.y)

# Calculate ageCentered and ageCenteredQuadratic
player_stats_seasonal_offense$ageCentered20 <- player_stats_seasonal_offense$age - 20
player_stats_seasonal_offense$ageCentered20Quadratic <- player_stats_seasonal_offense$ageCentered20 ^ 2

player_stats_seasonal_defense$ageCentered20 <- player_stats_seasonal_defense$age - 20
player_stats_seasonal_defense$ageCentered20Quadratic <- player_stats_seasonal_defense$ageCentered20 ^ 2

player_stats_seasonal_kicking$ageCentered20 <- player_stats_seasonal_kicking$age - 20
player_stats_seasonal_kicking$ageCentered20Quadratic <- player_stats_seasonal_kicking$ageCentered20 ^ 2

# Years of experience
player_stats_seasonal_offense$years_of_experience <- NULL
player_stats_seasonal_defense$years_of_experience <- NULL
player_stats_seasonal_kicking$years_of_experience <- NULL

player_stats_seasonal_offense <- player_stats_seasonal_offense %>% 
  dplyr::left_join(
    years_of_experience_offense[,c("player_id","season","years_of_experience")],
    by = c("player_id","season")
  )

player_stats_seasonal_defense <- player_stats_seasonal_defense %>% 
  dplyr::left_join(
    years_of_experience_offense[,c("player_id","season","years_of_experience")],
    by = c("player_id","season")
  )

player_stats_seasonal_kicking <- player_stats_seasonal_kicking %>% 
  dplyr::left_join(
    years_of_experience_offense[,c("player_id","season","years_of_experience")],
    by = c("player_id","season")
  )

# Arrange data
player_stats_seasonal_offense <- player_stats_seasonal_offense %>% 
  relocate(season, .after = player_id) %>% 
  arrange(player_display_name, season)

player_stats_seasonal_defense <- player_stats_seasonal_defense %>% 
  relocate(season, .after = player_id) %>% 
  arrange(player_display_name, season)

player_stats_seasonal_kicking <- player_stats_seasonal_kicking %>% 
  relocate(season, .after = player_id) %>% 
  arrange(player_display_name, season)
Code
# Save data
save(
  player_stats_seasonal_offense, player_stats_seasonal_defense, player_stats_seasonal_kicking,
  file = "./data/player_stats_seasonal.RData"
)

3.22 Session Info

At the end of each chapter in which R code is used, I provide the session information, which describes the system and operating system the code was run on and the versions of each package. That way, if you get different results from me, you can see which differ, to help with reproducibility. If you run the (all of) the exact same code as is provided in the text, in the exact same order, with the exact same setup (platform, operating system, package versions, etc.), you should get the exact same answer as is in the text. That is the idea of reproducibility—getting the exact same result with the exact same inputs. Reproducibility is crucial for studies to achieve greater confidence in their findings and to ensure better replicability of findings across studies.

Code
sessionInfo()
R version 4.4.1 (2024-06-14)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 22.04.4 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.20.so;  LAPACK version 3.10.0

locale:
 [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
 [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
 [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
[10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   

time zone: UTC
tzcode source: system (glibc)

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] forcats_1.0.0          stringr_1.5.1          dplyr_1.1.4           
 [4] purrr_1.0.2            readr_2.1.5            tidyr_1.3.1           
 [7] tibble_3.2.1           ggplot2_3.5.1          tidyverse_2.0.0       
[10] lubridate_1.9.3        progressr_0.14.0       nflplotR_1.3.1        
[13] nfl4th_1.0.4           nflfastR_4.6.1         nflreadr_1.4.1        
[16] ffanalytics_3.1.2.0003

loaded via a namespace (and not attached):
 [1] gtable_0.3.5      xfun_0.46         httr2_1.0.2       htmlwidgets_1.6.4
 [5] websocket_1.4.2   processx_3.8.4    lattice_0.22-6    tzdb_0.4.0       
 [9] vctrs_0.6.5       tools_4.4.1       ps_1.7.7          generics_0.1.3   
[13] curl_5.2.1        parallel_4.4.1    fansi_1.0.6       pkgconfig_2.0.3  
[17] Matrix_1.7-0      data.table_1.15.4 gt_0.11.0         readxl_1.4.3     
[21] lifecycle_1.0.4   compiler_4.4.1    munsell_0.5.1     chromote_0.2.0   
[25] janitor_2.2.0     codetools_0.2-20  snakecase_0.11.1  rrapply_1.2.7    
[29] htmltools_0.5.8.1 yaml_2.3.10       later_1.3.2       pillar_1.9.0     
[33] furrr_0.3.1       cachem_1.1.0      nlme_3.1-164      parallelly_1.38.0
[37] tidyselect_1.2.1  rvest_1.0.4       digest_0.6.36     stringi_1.8.4    
[41] future_1.34.0     listenv_0.9.1     splines_4.4.1     fastmap_1.2.0    
[45] grid_4.4.1        colorspace_2.1-1  cli_3.6.3         magrittr_2.0.3   
[49] utf8_1.2.4        withr_3.0.1       scales_1.3.0      promises_1.3.0   
[53] backports_1.5.0   rappdirs_0.3.3    xgboost_1.7.8.1   timechange_0.3.0 
[57] rmarkdown_2.27    httr_1.4.7        globals_0.16.3    cellranger_1.1.0 
[61] hms_1.1.3         memoise_2.0.1     evaluate_0.24.0   knitr_1.48       
[65] mgcv_1.9-1        rlang_1.1.4       Rcpp_1.0.13       glue_1.7.0       
[69] xml2_1.3.6        jsonlite_1.8.8    R6_2.5.1          fastrmodels_1.0.2

Feedback

Please consider providing feedback about this textbook, so that I can make it as helpful as possible. You can provide feedback at the following link: https://forms.gle/LsnVKwqmS1VuxWD18

Email Notification

The online version of this book will remain open access. If you want to know when the print version of the book is for sale, enter your email below so I can let you know.